#!/usr/bin/env python
# -*- coding:utf-8 -*-

import os, os.path
import re
import sys
import socket
#import base64
import time

import log
#from packet import Packet, enpack



#######################################################################

'''
if os.name == 'nt':
    # no such thing as WSAEPERM or error code 10001 according to winsock.h or MSDN
    EPERM = object()
    from errno import WSAEINVAL as EINVAL
    from errno import WSAEWOULDBLOCK as EWOULDBLOCK
    from errno import WSAEINPROGRESS as EINPROGRESS
    from errno import WSAEALREADY as EALREADY
    from errno import WSAECONNRESET as ECONNRESET
    from errno import WSAEISCONN as EISCONN
    from errno import WSAENOTCONN as ENOTCONN
    from errno import WSAEINTR as EINTR
    from errno import WSAENOBUFS as ENOBUFS
    from errno import WSAEMFILE as EMFILE
    # No such thing as WSAENFILE, either.
    ENFILE = object()
    # Nor ENOMEM
    ENOMEM = object()
    EAGAIN = EWOULDBLOCK
    from errno import WSAECONNRESET as ECONNABORTED
else:
    from errno import EPERM
    from errno import EINVAL
    from errno import EWOULDBLOCK
    from errno import EINPROGRESS
    from errno import EALREADY
    from errno import ECONNRESET
    from errno import EISCONN
    from errno import ENOTCONN
    from errno import EINTR
    from errno import ENOBUFS
    from errno import EMFILE
    from errno import ENFILE
    from errno import ENOMEM
    from errno import EAGAIN
    from errno import ECONNABORTED
'''
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
     ECONNREFUSED, ENOTCONN, ESHUTDOWN, EINTR, EISCONN, errorcode

class   TCPChannel(object) :

    ac_in_buffer_size       = 65536
    ac_out_buffer_size      = 65536

    def __init__(self, address) :
        self.address = address
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        #s.setblocking(0)
        #if fcntl and hasattr(fcntl, 'FD_CLOEXEC'):
        #    old = fcntl.fcntl(s.fileno(), fcntl.F_GETFD)
        #    fcntl.fcntl(s.fileno(), fcntl.F_SETFD, old | fcntl.FD_CLOEXEC)
        if 'posix' == os.name :
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        self.connected = False
        self.terminator = '\n'

        self.ac_in_buffer = ''
        self.ac_out_buffer = ''

        self.reconnect()
        # __init__()

    def __del__(self) :
        self.handleClose()
        # __del__()

    def handleClose(self) :
        if self.socket :
            self.socket.close()
            self.connected = False
        # handleClose()

    def reconnect(self) :
        if self.connected : return

        errno = self.socket.connect_ex(self.address)
        if errno in (0, EISCONN) :
            self.connected = True
        elif errno in (EINPROGRESS, EALREADY, EWOULDBLOCK):
            self.connected = False
        else :
            self.connected = False
            log.error('fail to connect to "%s" -- [%d] %s', self.address, errno, errorcode[errno])
            #raise socket.error, (errno, errorcode[errno])
        # reconnect()

    def sendRecord(self, record) :
        out_buf = record + self.terminator
        if not self.connected :
            self.reconnect()

        while self.connected :
            if len(out_buf) < 1 :
                return True

            try :
                num_sent = self.socket.send(out_buf)
                if num_sent :
                    out_buf = out_buf[ num_sent: ]
            except socket.error, why :
                #self.handle_error()
                if why[0] == EWOULDBLOCK:
                    continue
                else :
                    log.error('fail to send record to "%s" -- %s', self.address, why[1])
                    raise
                return  False

        if not self.connected :
            return False
        return  True
        # sendRecord()

    def recvRecord(self) :
        if not self.connected :
            self.reconnect()

        while self.connected :
            try:
                data = self.socket.recv (self.ac_in_buffer_size)
            except socket.error, why:
                # winsock sometimes throws ENOTCONN
                if why[0] in [ECONNREFUSED, ECONNRESET, ENOTCONN, ESHUTDOWN]:
                    self.handleClose()
                    return None
                else:
                    raise
                ##self.handle_error()
                ##return None

            if len(data) < 1 :
                # zero data read, may be socket broken
                log.error('receive ZERO bytes from socket, pipe broken.')
                self.handleClose()
                return None
                
            self.ac_in_buffer = self.ac_in_buffer + data

            lb = len(self.ac_in_buffer)
            terminator = self.terminator        ##self.getTerminator()
            terminator_len = len(terminator)
            index = self.ac_in_buffer.find(terminator)
            if index != -1:
                # we found the terminator
                if index > 0:
                    # don't bother reporting the empty string (source of subtle bugs)
                    record = self.ac_in_buffer[:index]
                else :
                    record = None
                self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]

                if record :
                    return record

        return None
        # recvRecord()
    # class TCPChannel


#######################################################################

def compact_traceback():
    t, v, tb = sys.exc_info()
    tbinfo = []
    assert tb # Must have a traceback
    while tb:
        tbinfo.append((
            tb.tb_frame.f_code.co_filename,
            tb.tb_frame.f_code.co_name,
            str(tb.tb_lineno)
            ))
        tb = tb.tb_next

    # just to be safe
    del tb

    file, function, line = tbinfo[-1]
    info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
    return (file, function, line), t, v, info

